﻿using System;
using System.Windows;
using System.Windows.Media;

namespace LooseChart
{
    /// <summary>
    /// 可负数log
    /// </summary>
    public class NegativeLogAxis : IAxis
    {
        /// <summary>
        /// log的底,尽可能用正整数或e
        /// </summary>
        public double LogBase
        {
            get { return (double)GetValue(LogBaseProperty); }
            set { SetValue(LogBaseProperty, value); }
        }
        public static readonly DependencyProperty LogBaseProperty = DependencyProperty.Register("LogBase", typeof(double), typeof(NegativeLogAxis), new PropertyMetadata(10d, (d, e) =>
        {
            if (d is IAxis axis) axis.RedrawAll();
        }));

        /// <summary>
        /// 线性显示的次方数,0附近次方数为线性显示
        /// </summary>
        public uint LinePowerCount
        {
            get { return (uint)GetValue(LinePowerCountProperty); }
            set { SetValue(LinePowerCountProperty, value); }
        }
        public static readonly DependencyProperty LinePowerCountProperty = DependencyProperty.Register("LinePowerCount", typeof(uint), typeof(NegativeLogAxis), new PropertyMetadata(1u, (d, e) =>
        {
            if (d is IAxis axis) axis.RedrawAll();
        }));

        /// <summary>
        /// 线性压缩倍数,非0正数
        /// </summary>
        public double LineCompress
        {
            get { return (double)GetValue(LineCompressProperty); }
            set { SetValue(LineCompressProperty, value); }
        }
        public static readonly DependencyProperty LineCompressProperty = DependencyProperty.Register("LineCompress", typeof(double), typeof(NegativeLogAxis), new PropertyMetadata(1d, (d, e) =>
        {
            if (d is IAxis axis) axis.RedrawAll();
        }));

        public NegativeLogAxis() : base() { }

        /// <summary>
        /// 实际值转坐标值
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public override double ValueToAxisValue(double value, ValueAxisConvertParamBase param)
        {
            uint linePowerCount = 1;
            double logBase = 10d;
            double lineCompress = 1d;
            if (param is NegativeLogValueAxisConvertParam p)
            {
                if (p.LinePowerCount > 0) linePowerCount = p.LinePowerCount;
                logBase = p.LogBase;
                if (p.LineCompress > 0) lineCompress = p.LineCompress;
            }
            var lineMaxValue = Math.Pow(logBase, linePowerCount);
            if (double.IsNaN(value)) return value;
            else if (double.IsPositiveInfinity(value)) return double.MaxValue;
            else if (double.IsNegativeInfinity(value)) return double.MinValue;
            else if (value >= lineMaxValue) return Math.Log(value, logBase) - linePowerCount + linePowerCount / lineCompress;
            else if (value <= -lineMaxValue) return -Math.Log(-value, logBase) + linePowerCount - linePowerCount / lineCompress;
            else return value * linePowerCount / lineMaxValue / lineCompress;
        }

        /// <summary>
        /// 坐标值转实际值
        /// </summary>
        /// <param name="axisvalue"></param>
        /// <returns></returns>
        public override double AxisValueToValue(double axisvalue, ValueAxisConvertParamBase param)
        {
            uint linePowerCount = 1;
            double logBase = 10d;
            double lineCompress = 1d;
            if (param is NegativeLogValueAxisConvertParam p)
            {
                if (p.LinePowerCount > 0) linePowerCount = p.LinePowerCount;
                logBase = p.LogBase;
                if (p.LineCompress > 0) lineCompress = p.LineCompress;
            }
            var lineMaxValue = Math.Pow(logBase, linePowerCount);
            if (double.IsNaN(axisvalue)) return axisvalue;
            else if (double.IsPositiveInfinity(axisvalue)) return double.MaxValue;
            else if (double.IsNegativeInfinity(axisvalue)) return double.MinValue;
            else if (axisvalue >= linePowerCount / lineCompress) return Math.Pow(logBase, axisvalue + linePowerCount - linePowerCount / lineCompress);
            else if (axisvalue <= -linePowerCount / lineCompress) return -Math.Pow(logBase, -axisvalue + linePowerCount - linePowerCount / lineCompress);
            else return axisvalue * lineCompress * lineMaxValue / linePowerCount;
        }

        private double GetShowingAxisValue(double value, ValueAxisConvertParamBase param)
        {
            uint linePowerCount = 1;
            double logBase = 10d;
            if (param is NegativeLogValueAxisConvertParam p)
            {
                if (p.LinePowerCount > 0) linePowerCount = p.LinePowerCount;
                logBase = p.LogBase;
            }
            var lineMaxValue = Math.Pow(logBase, linePowerCount);
            if (double.IsNaN(value)) return value;
            else if (double.IsPositiveInfinity(value)) return double.MaxValue;
            else if (double.IsNegativeInfinity(value)) return double.MinValue;
            else if (value >= lineMaxValue) return Math.Log(value, logBase);
            else if (value <= -lineMaxValue) return -Math.Log(-value, logBase);
            else return value * linePowerCount / lineMaxValue;
        }

        private double GetShowingValue(double axisvalue, ValueAxisConvertParamBase param)
        {
            uint linePowerCount = 1;
            double logBase = 10d;
            if (param is NegativeLogValueAxisConvertParam p)
            {
                if (p.LinePowerCount > 0) linePowerCount = p.LinePowerCount;
                logBase = p.LogBase;
            }
            var lineMaxValue = Math.Pow(logBase, linePowerCount);
            if (double.IsNaN(axisvalue)) return axisvalue;
            else if (double.IsPositiveInfinity(axisvalue)) return double.MaxValue;
            else if (double.IsNegativeInfinity(axisvalue)) return double.MinValue;
            else if (axisvalue >= linePowerCount) return Math.Pow(logBase, axisvalue);
            else if (axisvalue <= -linePowerCount) return -Math.Pow(logBase, -axisvalue);
            else return axisvalue * lineMaxValue / linePowerCount;
        }

        /// <summary>
        /// 获取实际值和坐标值转换参数
        /// </summary>
        /// <returns></returns>
        public override ValueAxisConvertParamBase GetValueAxisConvertParam()
        {
            return new NegativeLogValueAxisConvertParam()
            {
                LogBase = this.LogBase,
                LinePowerCount = this.LinePowerCount,
                LineCompress = this.LineCompress
            };
        }

        public override void Draw()
        {
            if (!this.IsLoaded) return;
            var valuelocationparam = GetConvertParam();
            var valueaxisconvertparam = valuelocationparam.ValueAxisParam as NegativeLogValueAxisConvertParam;
            if (valueaxisconvertparam.LogBase <= 1) return;
            var showingMaxAxis = GetShowingAxisValue(Max, valueaxisconvertparam);
            var showingMinAxis = GetShowingAxisValue(Min, valueaxisconvertparam);

            var range = showingMaxAxis - showingMinAxis;
            var count = Convert.ToInt32(valuelocationparam.Length / (Math.Max(showingMaxAxis.ToString("N0").Length, 4) * FontSize));
            if (count <= 0) return;
            var subrange = GetSubRange(range, count);
            var subpowercount = Convert.ToInt64(subrange);
            if (subpowercount < 1) subpowercount = 1;

            var showedTempMax = Convert.ToInt64(Math.Ceiling(showingMaxAxis));
            var showedTempMin = Convert.ToInt64(Math.Floor(showingMinAxis));

            string logBaseString = valueaxisconvertparam.LogBase.ToString();
            if (valueaxisconvertparam.LogBase == Math.E) logBaseString = "e";
            else if (valueaxisconvertparam.LogBase == Math.PI) logBaseString = "π";
#if NET5_0_OR_GREATER
            else if (valueaxisconvertparam.LogBase == Math.Tau) logBaseString = "τ";
#endif
            var tempLogBase = Convert.ToInt32(Math.Floor(valueaxisconvertparam.LogBase));
            var linerange = Math.Pow(valueaxisconvertparam.LogBase, valueaxisconvertparam.LinePowerCount) / valueaxisconvertparam.LinePowerCount / tempLogBase;
            var subcount = tempLogBase - 1;
            using (var dc = DV.RenderOpen())
            {
                dc.DrawRectangle(Background, null, new Rect(0, 0, this.ActualWidth, this.ActualHeight));
                if (valuelocationparam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(0, 0), new Point(this.ActualWidth, 0));
                else if (valuelocationparam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, 0), new Point(this.ActualWidth, this.ActualHeight));
                else dc.DrawLine(LinePen, new Point(0, 0), new Point(0, this.ActualHeight));
                for (long i = showedTempMin - 1; i <= showedTempMax + 1; i++)
                {
                    var value = GetShowingValue(i, valueaxisconvertparam);
                    var valuelocation = GetValueLocation(value, valuelocationparam);
                    if (Math.Abs(i) >= valueaxisconvertparam.LinePowerCount)//指数部分
                    {
                        if (valuelocation >= 0 && valuelocation <= valuelocationparam.Length)
                        {
                            if (i % subpowercount == 0)//显示文字指数
                            {
                                var bString = i > 0 ? logBaseString : string.Concat("-", logBaseString);
                                var pString = i > 0 ? i.ToString() : (-i).ToString();
                                var vft = new FormattedText(bString, System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, DefaultTypeface, FontSize, Foreground, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                                var pft = new FormattedText(pString, System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, DefaultTypeface, PowerFontSize, Foreground, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                                var vftg = vft.BuildGeometry(PointZero);
                                var pftg = pft.BuildGeometry(PointZero);
                                if (valuelocation >= 0 && valuelocation <= valuelocationparam.Length)
                                {
                                    if (valuelocationparam.AxisType == AxisType.X)
                                    {
                                        dc.DrawLine(LinePen, new Point(valuelocation, 0), new Point(valuelocation, 5));
                                        vftg.Transform = new MatrixTransform(1, 0, 0, 1, valuelocation - (vft.Width + pft.Width) / 2, 5);
                                        dc.DrawGeometry(Foreground, StringPen, vftg);
                                        pftg.Transform = new MatrixTransform(1, 0, 0, 1, valuelocation + vft.Width / 2 - pft.Width / 2, 5);
                                        dc.DrawGeometry(Foreground, StringPen, pftg);
                                    }
                                    else if (valuelocationparam.AxisType == AxisType.Y)
                                    {
                                        dc.DrawLine(LinePen, new Point(this.ActualWidth, valuelocation), new Point(this.ActualWidth - 5, valuelocation));
                                        vftg.Transform = new MatrixTransform(0, -1, 1, 0, this.ActualWidth - 10 - FontSize, valuelocation + (vft.Width + pft.Width) / 2);
                                        dc.DrawGeometry(Foreground, StringPen, vftg);
                                        pftg.Transform = new MatrixTransform(0, -1, 1, 0, this.ActualWidth - 10 - FontSize, valuelocation - vft.Width / 2 + pft.Width / 2);
                                        dc.DrawGeometry(Foreground, StringPen, pftg);
                                    }
                                    else
                                    {
                                        dc.DrawLine(LinePen, new Point(0, valuelocation), new Point(5, valuelocation));
                                        vftg.Transform = new MatrixTransform(0, 1, -1, 0, 10 + FontSize, valuelocation - (vft.Width + pft.Width) / 2);
                                        dc.DrawGeometry(Foreground, StringPen, vftg);
                                        pftg.Transform = new MatrixTransform(0, 1, -1, 0, 10 + FontSize, valuelocation + vft.Width / 2 - pft.Width / 2);
                                        dc.DrawGeometry(Foreground, StringPen, pftg);
                                    }
                                }
                            }
                            else
                            {
                                if (valuelocationparam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(valuelocation, 0), new Point(valuelocation, 3));
                                else if (valuelocationparam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, valuelocation), new Point(this.ActualWidth - 3, valuelocation));
                                else dc.DrawLine(LinePen, new Point(0, valuelocation), new Point(3, valuelocation));
                            }
                        }
                        if (subpowercount == 1)
                        {
                            for (int j = 2; j < tempLogBase; j++)
                            {
                                var sublocation = GetValueLocation(value * j, valuelocationparam);
                                if (sublocation > 0 && sublocation < valuelocationparam.Length)
                                {
                                    if (valuelocationparam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(sublocation, 0), new Point(sublocation, 3));
                                    else if (valuelocationparam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, sublocation), new Point(this.ActualWidth - 3, sublocation));
                                    else dc.DrawLine(LinePen, new Point(0, sublocation), new Point(3, sublocation));
                                }
                            }
                        }
                    }
                    else if (i == 0)//线性部分
                    {
                        var vString = value.ToString();
                        var vft = new FormattedText(vString, System.Globalization.CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, DefaultTypeface, FontSize, Foreground, VisualTreeHelper.GetDpi(this).PixelsPerDip).BuildGeometry(PointZero);
                        if (valuelocation >= 0 && valuelocation <= valuelocationparam.Length)
                        {
                            if (valuelocationparam.AxisType == AxisType.X)
                            {
                                dc.DrawLine(LinePen, new Point(valuelocation, 0), new Point(valuelocation, 5));
                                vft.Transform = new MatrixTransform(1, 0, 0, 1, valuelocation - vString.Length * FontSize / 3, 5);
                            }
                            else if (valuelocationparam.AxisType == AxisType.Y)
                            {
                                dc.DrawLine(LinePen, new Point(this.ActualWidth, valuelocation), new Point(this.ActualWidth - 5, valuelocation));
                                vft.Transform = new MatrixTransform(0, -1, 1, 0, this.ActualWidth - 10 - FontSize, valuelocation + vString.Length * FontSize / 3);
                            }
                            else
                            {
                                dc.DrawLine(LinePen, new Point(0, valuelocation), new Point(5, valuelocation));
                                vft.Transform = new MatrixTransform(0, 1, -1, 0, 10 + FontSize, valuelocation - vString.Length * FontSize / 3);
                            }
                            dc.DrawGeometry(Foreground, StringPen, vft);
                        }
                        for (int j = 1; j < tempLogBase; j++)
                        {
                            {
                                var subv = GetShowingValue(j * valueaxisconvertparam.LinePowerCount * 1d / tempLogBase, valueaxisconvertparam);
                                var sublocation = GetValueLocation(subv, valuelocationparam);
                                if (sublocation >= 0 && sublocation <= valuelocationparam.Length)
                                {
                                    if (valuelocationparam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(sublocation, 0), new Point(sublocation, 3));
                                    else if (valuelocationparam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, sublocation), new Point(this.ActualWidth - 3, sublocation));
                                    else dc.DrawLine(LinePen, new Point(0, sublocation), new Point(3, sublocation));
                                }
                            }
                            {
                                var subv = GetShowingValue(-j * valueaxisconvertparam.LinePowerCount * 1d / tempLogBase, valueaxisconvertparam);
                                var sublocation = GetValueLocation(subv, valuelocationparam);
                                if (sublocation >= 0 && sublocation <= valuelocationparam.Length)
                                {
                                    if (valuelocationparam.AxisType == AxisType.X) dc.DrawLine(LinePen, new Point(sublocation, 0), new Point(sublocation, 3));
                                    else if (valuelocationparam.AxisType == AxisType.Y) dc.DrawLine(LinePen, new Point(this.ActualWidth, sublocation), new Point(this.ActualWidth - 3, sublocation));
                                    else dc.DrawLine(LinePen, new Point(0, sublocation), new Point(3, sublocation));
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// 实际值和坐标值转换参数
    /// </summary>
    public class NegativeLogValueAxisConvertParam : ValueAxisConvertParamBase
    {
        /// <summary>
        /// log的底,尽可能用正整数或e
        /// </summary>
        public double LogBase { get; set; }
        /// <summary>
        /// 线性显示的次方数
        /// </summary>
        public uint LinePowerCount { get; set; }
        /// <summary>
        /// 线性压缩倍数
        /// </summary>
        public double LineCompress { get; set; }
    }
}
